#ifdef ENABLE_MAPLE_SAN

#include "asan_function.h"

#include <string_utils.h>

#include <stack>
#include <typeinfo>

#include "asan_interfaces.h"
#include "asan_stackvar.h"
#include "me_cfg.h"
#include "me_function.h"
#include "mir_builder.h"
#include "mpl_logging.h"
#include "opcode_info.h"
#include "san_common.h"

namespace maple {
bool isBlacklist(int k) {
  return (k == 120 || k == 125);
}

void dumpFunc(MeFunction &mefunc) {
  StmtNodes &stmtNodes = mefunc.GetMirFunc()->GetBody()->GetStmtNodes();
  for (StmtNode &stmt : stmtNodes) {
    stmt.Dump(0);
  }
}

bool AddressSanitizer::instrumentFunction(MeFunction &mefunc) {
  if (neighborOptSwitch){
    prepareInfo(mefunc);
    DumpSkipPos();
  }

  MIRBuilder *builder = mefunc.GetMIRModule().GetMIRBuilder();
  this->func = &mefunc;
  if (mefunc.GetMirFunc()->GetAttr(FUNCATTR_extern)) {
    return false;
  }
  if (mefunc.GetName().find("__asan_") == 0 || mefunc.GetName().find("__san_cov_") == 0) {
    return false;
  }

  bool functionModified = false;

  LogInfo::MapleLogger() << "ASAN instrumenting: " << mefunc.GetName() << "\n";

  initializeCallbacks(mefunc.GetMIRModule());

  // When exit this function, the destructor will clean relatives
  FunctionStateRAII cleanupObj(this);

  maybeInsertDynamicShadowAtFunctionEntry(mefunc);

  std::vector<StmtBlockNodePair> toInstrument = getAllOrderedStmtNodeList(mefunc.GetMirFunc()->GetBody());
  std::vector<StmtBlockNodePair> noReturnCalls;

  for (auto stmtBlockPair : toInstrument) {
    StmtNode* stmtptr = stmtBlockPair.first;
    CallNode *callNode = dynamic_cast<CallNode *>(stmtptr);
    if (callNode != nullptr) {
      MIRFunction *calleeFunc = GlobalTables::GetFunctionTable().GetFunctionFromPuidx(callNode->GetPUIdx());
      if (calleeFunc->NeverReturns() || calleeFunc->GetName() == "exit") {
        noReturnCalls.push_back(stmtBlockPair);
      }
    }
  }

  int numInstrumented = 0;
  // The code below checks and instruments all indirect memory access
  for (auto stmtBlockPair : toInstrument) {
    StmtNode* stmtptr = stmtBlockPair.first;
    BlockNode* blockptr = stmtBlockPair.second;
    std::vector<MemoryAccess> memAccVec = isInterestingMemoryAccess(stmtptr);
    if (memAccVec.size() > 0) {
      instrumentMop(stmtptr, memAccVec, blockptr);
    } else {
      instrumentMemIntrinsic(dynamic_cast<IntrinsiccallNode *>(stmtptr), blockptr);
    }
    numInstrumented++;
  }

  FunctionStackPoisoner fsp(mefunc, *this);
  // variables are replaced with pointers after fsp
  bool changedStack = fsp.runOnFunction();

  for (auto stmtBlockPair : noReturnCalls) {
    MapleVector<BaseNode *> args(builder->GetCurrentFuncCodeMpAllocator()->Adapter());
    CallNode *callNode = builder->CreateStmtCall(AsanHandleNoReturnFunc->GetPuidx(), args);
    StmtNode* stmtptr = stmtBlockPair.first;
    BlockNode* blockptr = stmtBlockPair.second;
    blockptr->InsertBefore(stmtptr, callNode);
  }

  int check_env = SANRAZOR_MODE();
  std::vector<int> stmt_id_list;
  bool doSanrazor = (check_env > 0) && (numInstrumented > 0 || changedStack || !noReturnCalls.empty());
  if (doSanrazor) {
    functionModified = true;
    SanrazorProcess(mefunc);
  }
  // dump IRs of each block
  // dumpFunc(mefunc);
  LogInfo::MapleLogger() << "ASAN done instrumenting: " << functionModified << " " << mefunc.GetName() << "\n";

  return functionModified;
}

void AddressSanitizer::SanrazorProcess(MeFunction &mefunc) {
  // MIRBuilder *builder = mefunc.GetMIRModule().GetMIRBuilder();
  LogInfo::MapleLogger() << "****************SANRAZOR instrumenting****************"
                         << "\n";
  LogInfo::MapleLogger() << "****************SANRAZOR Done****************"
                         << "\n";
}

void AddressSanitizer::instrumentMemIntrinsic(IntrinsiccallNode *stmtNode, BlockNode* block) {
  if (stmtNode == nullptr) {
    return;
  }

  switch (stmtNode->GetIntrinsic()) {
    case INTRN_C_memset: {
      MapleVector<BaseNode *> args(module->GetMPAllocator().Adapter());
      args.emplace_back(stmtNode->Opnd(0));
      args.emplace_back(stmtNode->Opnd(1));
      args.emplace_back(stmtNode->Opnd(2));

      CallNode *registerCallNode = module->GetMIRBuilder()->CreateStmtCall(AsanMemset->GetPuidx(), args);
      block->ReplaceStmt1WithStmt2(stmtNode, registerCallNode);
      return;
    }
    case INTRN_C_memmove: {
      MapleVector<BaseNode *> args(module->GetMPAllocator().Adapter());
      args.emplace_back(stmtNode->Opnd(0));
      args.emplace_back(stmtNode->Opnd(1));
      args.emplace_back(stmtNode->Opnd(2));

      CallNode *registerCallNode = module->GetMIRBuilder()->CreateStmtCall(AsanMemmove->GetPuidx(), args);
      block->ReplaceStmt1WithStmt2(stmtNode, registerCallNode);
      return;
    }
    case INTRN_C_memcpy: {
      MapleVector<BaseNode *> args(module->GetMPAllocator().Adapter());
      args.emplace_back(stmtNode->Opnd(0));
      args.emplace_back(stmtNode->Opnd(1));
      args.emplace_back(stmtNode->Opnd(2));

      CallNode *registerCallNode = module->GetMIRBuilder()->CreateStmtCall(AsanMemcpy->GetPuidx(), args);
      block->ReplaceStmt1WithStmt2(stmtNode, registerCallNode);
      return;
    }
    default: {
      return;
    }
  }
}

MemoryAccess AddressSanitizer::getIassignMemoryAccess(IassignNode &iassign) {
  MIRType *mirType = GlobalTables::GetTypeTable().GetTypeFromTyIdx(iassign.GetTyIdx());
  MIRPtrType *pointerType = static_cast<MIRPtrType *>(mirType);
  MIRType *pointedTy = GlobalTables::GetTypeTable().GetTypeFromTyIdx(pointerType->GetPointedTyIdx());
  size_t align = pointedTy->GetAlign();
  if (pointedTy->IsStructType()) {
    MIRStructType *mirStructType = dynamic_cast<MIRStructType *>(pointedTy);
    FieldID fieldID = iassign.GetFieldID();
    if (fieldID > 0) {
      pointedTy = mirStructType->GetFieldType(fieldID);
      if (fieldID == 1 && mirStructType->GetFieldOffsetFromBaseAddr(fieldID).byteOffset == 0) {
        // I do not know why the first field can be inequal to the whole structure's align sometimes
        align = mirStructType->GetAlign();
      } else {
        align = pointedTy->GetAlign();
      }
    } else {
      // align = pointedTy->GetSize();
      align = mirStructType->GetAlign();
    }
  }
  BaseNode *addr =
      module->GetMIRBuilder()->CreateExprIaddrof(PTY_u64, iassign.GetTyIdx(), iassign.GetFieldID(), iassign.Opnd(0));
  MemoryAccess memoryAccess = {&iassign, true, pointedTy->GetSize() << 3, align, addr};
  return memoryAccess;
}

MemoryAccess AddressSanitizer::getIassignoffMemoryAccess(IassignoffNode &iassignoff) {
  int32 offset = iassignoff.GetOffset();
  BaseNode *addrNode = iassignoff.GetBOpnd(0);
  PrimType primType = iassignoff.GetPrimType();
  PrimType addrPrimType = addrNode->GetPrimType();
  BaseNode *addrExpr = nullptr;
  size_t primTypeSize = GetPrimTypeSize(primType);
  if (offset == 0) {
    addrExpr = addrNode;
  } else {
    MIRType *mirType = module->CurFuncCodeMemPool()->New<MIRType>(MIRTypeKind::kTypePointer, addrPrimType);
    MIRIntConst *offsetConstVal = module->CurFuncCodeMemPool()->New<MIRIntConst>(offset, *mirType);
    ConstvalNode *offsetNode = module->CurFuncCodeMemPool()->New<ConstvalNode>(addrPrimType);
    offsetNode->SetConstVal(offsetConstVal);
    addrExpr = module->CurFuncCodeMemPool()->New<BinaryNode>(OP_add, addrPrimType, addrNode, offsetNode);
  }
  BaseNode *addr = module->GetMIRBuilder()->CreateExprIaddrof(addrPrimType, TyIdx(primType), 0, addrExpr);
  MemoryAccess memoryAccess = {&iassignoff, true, primTypeSize << 3, primTypeSize, addr};
  return memoryAccess;
}

MemoryAccess AddressSanitizer::getIreadMemoryAccess(IreadNode &iread, StmtNode *stmtNode) {
  MIRType *mirType = GlobalTables::GetTypeTable().GetTypeFromTyIdx(iread.GetTyIdx());
  MIRPtrType *pointerType = static_cast<MIRPtrType *>(mirType);
  MIRType *pointedTy = GlobalTables::GetTypeTable().GetTypeFromTyIdx(pointerType->GetPointedTyIdx());
  size_t align = pointedTy->GetAlign();
  if (pointedTy->IsStructType()) {
    MIRStructType *mirStructType = dynamic_cast<MIRStructType *>(pointedTy);
    FieldID fieldID = iread.GetFieldID();
    if (fieldID > 0) {
      pointedTy = mirStructType->GetFieldType(fieldID);
      if (fieldID == 1 && mirStructType->GetFieldOffsetFromBaseAddr(fieldID).byteOffset == 0) {
        // I do not know why the first field can be inequal to the whole structure's align sometimes
        align = mirStructType->GetAlign();
      } else {
        align = pointedTy->GetAlign();
      }
    } else {
      // align = pointedTy->GetSize();
      align = mirStructType->GetAlign();
    }
  }
  BaseNode *addr =
      module->GetMIRBuilder()->CreateExprIaddrof(PTY_u64, iread.GetTyIdx(), iread.GetFieldID(), iread.Opnd(0));
  MemoryAccess memoryAccess = {stmtNode, false, pointedTy->GetSize() << 3, align, addr};
  return memoryAccess;
}

std::vector<MemoryAccess> AddressSanitizer::isInterestingMemoryAccess(StmtNode *stmtNode) {
  std::vector<MemoryAccess> memAccess;
  if (LocalDynamicShadow == stmtNode) {
    return memAccess;
  }

  std::stack<BaseNode *> baseNodeStack;
  baseNodeStack.push(stmtNode);
  while (!baseNodeStack.empty()) {
    BaseNode *baseNode = baseNodeStack.top();
    CHECK_FATAL(baseNode != nullptr, "Invalid IR node pointer.");
    baseNodeStack.pop();
    switch (baseNode->GetOpCode()) {
      case OP_iassign: {
        IassignNode *iassign = dynamic_cast<IassignNode *>(baseNode);
        CHECK_FATAL((iassign != nullptr), "Invalid IR node with OpCode OP_iassign");
        struct MemoryAccess memoryAccess = getIassignMemoryAccess(*iassign);
        /* MEDoNeighborOpt */
        int64 newSize = checkNeighborOpt(iassign);
        LogInfo::MapleLogger() << "checkNeighborOpt: "<< newSize <<"\n";
        if (newSize > 0)
          memoryAccess.typeSize = newSize << 3;
        if (newSize >= 0)
          memAccess.emplace_back(memoryAccess);
        
        // the rhs-expr can still read from somewhere, push it to stack
        baseNodeStack.push(iassign->Opnd(1));
        break;
      }
      case OP_iassignoff: {
        IassignoffNode *iassignoff = dynamic_cast<IassignoffNode *>(baseNode);
        CHECK_FATAL((iassignoff != nullptr), "Invalid IR node with OpCode OP_iassignoff");
        struct MemoryAccess memoryAccess = getIassignoffMemoryAccess(*iassignoff);
        /* MEDoNeighborOpt */
        int64 newSize = checkNeighborOpt(iassignoff);
        LogInfo::MapleLogger() << "checkNeighborOpt: " << newSize << "\n";
        if (newSize > 0)
          memoryAccess.typeSize = newSize << 3;
        if (newSize >= 0)
          memAccess.emplace_back(memoryAccess);

        // the rhs-expr can still read from somewhere, push it to stack
        baseNodeStack.push(iassignoff->GetBOpnd(1));
        break;
      }
      case OP_iassignfpoff:
      case OP_iassignpcoff:
        break;
      case OP_iread: {
        IreadNode *iread = nullptr;
        if (baseNode->IsSSANode()) {
          iread = dynamic_cast<IreadNode *>(dynamic_cast<IreadSSANode *>(baseNode)->GetNoSSANode());
        } else {
          iread = dynamic_cast<IreadNode *>(baseNode);
        }
        CHECK_FATAL((iread != nullptr), "Invalid IR node with OpCode OP_iread.");
        struct MemoryAccess memoryAccess = getIreadMemoryAccess(*iread, stmtNode);
        /* MEDoNeighborOpt */
        int64 newSize = checkNeighborOpt(iread);
        LogInfo::MapleLogger() << "checkNeighborOpt: " << newSize << "\n";
        if (newSize > 0)
          memoryAccess.typeSize = newSize << 3;
        if (newSize >= 0)
          memAccess.emplace_back(memoryAccess);
        break;
      }
      case OP_ireadoff:
      case OP_ireadfpoff:
      case OP_ireadpcoff:
        break;
      default: {
      }
    }
    for (size_t j = 0; j < baseNode->NumOpnds(); ++j) {
      if (baseNode->GetOpCode() == OP_return) continue;
      baseNodeStack.push(baseNode->Opnd(j));
    }
  }
  LogInfo::MapleLogger() << "ASAN isInterestingMemoryAccess is done.\n";
  return memAccess;
}

void AddressSanitizer::initializeCallbacks(const MIRModule &mirModule) {
  MIRBuilder *mirBuilder = mirModule.GetMIRBuilder();
  MIRType *voidType = GlobalTables::GetTypeTable().GetVoid();
  MIRType *Int32Type = GlobalTables::GetTypeTable().GetPrimType(PTY_i32);
  MIRType *Int8PtrType =
      GlobalTables::GetTypeTable().GetOrCreatePointerType(GlobalTables::GetTypeTable().GetInt8()->GetTypeIndex());

#ifdef ENABLERBTREE
  AsanRBTSafetyCheck = getOrInsertFunction(mirBuilder, "__asan_rbt_safety_check", voidType, {IntPtrTy, Int32Type});
  AsanRBTStackInsert = getOrInsertFunction(mirBuilder, "__asan_rbt_stack_insert", voidType, {IntPtrTy, Int32Type});
  AsanRBTStackDelete = getOrInsertFunction(mirBuilder, "__asan_rbt_stack_delete", voidType, {IntPtrTy, Int32Type});
#else
  for (size_t AccessIsWrite = 0; AccessIsWrite <= 1; AccessIsWrite++) {
    const std::string TypeStr = AccessIsWrite ? "store" : "load";
    AsanErrorCallbackSized[AccessIsWrite] = getOrInsertFunction(
        mirBuilder, (kAsanReportErrorTemplate + TypeStr + "_n").c_str(), voidType, {IntPtrTy, IntPtrTy});
    for (size_t AccessSizeIndex = 0; AccessSizeIndex < kNumberOfAccessSizes; AccessSizeIndex++) {
      const std::string Suffix = TypeStr + std::to_string(1ULL << AccessSizeIndex);
      AsanErrorCallback[AccessIsWrite][AccessSizeIndex] =
          getOrInsertFunction(mirBuilder, (kAsanReportErrorTemplate + Suffix).c_str(), voidType, {IntPtrTy});
    }
  }
#endif

  AsanMemmove = getOrInsertFunction(mirBuilder, "__asan_memmove", Int8PtrType, {Int8PtrType, Int8PtrType, IntPtrTy});
  AsanMemcpy = getOrInsertFunction(mirBuilder, "__asan_memcpy", Int8PtrType, {Int8PtrType, Int8PtrType, IntPtrTy});
  AsanMemset = getOrInsertFunction(mirBuilder, "__asan_memset", Int8PtrType, {Int8PtrType, Int32Type, IntPtrTy});

  AsanHandleNoReturnFunc = getOrInsertFunction(mirBuilder, kAsanHandleNoReturnName, voidType, {});
}

void AddressSanitizer::maybeInsertDynamicShadowAtFunctionEntry(const MeFunction &F) {
  if (Mapping.Offset != kDynamicShadowSentinel) {
    return;
  }
  MIRBuilder *mirBuilder = F.GetMIRModule().GetMIRBuilder();
  MIRSymbol *GlobalDynamicAddress = mirBuilder->GetOrCreateGlobalDecl(kAsanShadowMemoryDynamicAddress, *IntPtrTy);
  DreadNode *dreadNode = mirBuilder->CreateDread(*GlobalDynamicAddress, PTY_ptr);
  MIRType *Int64PtrTy = GlobalTables::GetTypeTable().GetOrCreatePointerType(IntPtrTy->GetTypeIndex());
  LocalDynamicShadow = mirBuilder->CreateExprIread(*IntPtrTy, *Int64PtrTy, 0, dreadNode);
}

void AddressSanitizer::instrumentMop(StmtNode *I, std::vector<MemoryAccess> &memoryAccess, BlockNode* block) {
  assert(memoryAccess.size() > 0);

  size_t granularity = 1 << Mapping.Scale;
  for (MemoryAccess access : memoryAccess) {
    uint64 newMemSize = access.typeSize;
    // LogInfo::MapleLogger() << "doInstrumentAddress:"<<access.alignment<< " " <<granularity<<" " << newSize << access.typeSize <<"\n";
    AsanVirtualBlock asanBlock = doInstrumentAddress(I, access.ptrOperand, block, access.alignment, granularity, newMemSize, access.isWrite);
    // Save the code block of ASAN code
    AsanBlockListPtr->push_back(asanBlock);
  }
}

BaseNode *AddressSanitizer::memToShadow(BaseNode *Shadow, MIRBuilder &mirBuilder) {
  Shadow = mirBuilder.CreateExprBinary(OP_ashr, *GlobalTables::GetTypeTable().GetInt64(), Shadow,
                                       mirBuilder.CreateIntConst(Mapping.Scale, IntPtrPrim));
  if (Mapping.Offset == 0) {
    return Shadow;
  }
  BaseNode *ShadowBase;
  if (LocalDynamicShadow) {
    ShadowBase = LocalDynamicShadow;
  } else {
    ShadowBase = mirBuilder.CreateIntConst(Mapping.Offset, IntPtrPrim);
  }
  if (Mapping.OrShadowOffset) {
    return mirBuilder.CreateExprBinary(OP_lior, *GlobalTables::GetTypeTable().GetInt64(), Shadow, ShadowBase);
  } else {
    return mirBuilder.CreateExprBinary(OP_add, *GlobalTables::GetTypeTable().GetInt64(), Shadow, ShadowBase);
  }
}

AsanVirtualBlock AddressSanitizer::doInstrumentAddress(StmtNode *InsertBefore, BaseNode *Addr, BlockNode* block,
                         size_t Alignment, size_t Granularity, uint64_t TypeSize, bool IsWrite) {
  // Instrument a 1-, 2-, 4-, 8-, or 16- byte access with one check
  // if the data is properly aligned.
  if ((TypeSize == 8 || TypeSize == 16 || TypeSize == 32 || TypeSize == 64 || TypeSize == 128) &&
      (Alignment >= Granularity || Alignment == 0 || Alignment >= TypeSize / 8)) {
    return instrumentAddress(InsertBefore, Addr, block, TypeSize, IsWrite, nullptr);
  } else {
    return instrumentUnusualSizeOrAlignment(InsertBefore, Addr, block, TypeSize, IsWrite);
  }
}

AsanVirtualBlock AddressSanitizer::instrumentAddress(StmtNode *InsertBefore, BaseNode *Addr, BlockNode* block, uint64_t TypeSize,
                                         bool IsWrite, BaseNode *SizeArgument) {
  MIRBuilder *mirBuilder = module->GetMIRBuilder();
  AsanVirtualBlock asanBlock;

#ifdef ENABLERBTREE
  auto i32PrimTy = GlobalTables::GetTypeTable().GetInt32()->GetPrimType();
  MapleVector<BaseNode *> args(mirBuilder->GetCurrentFuncCodeMpAllocator()->Adapter());
  args.emplace_back(Addr);
  args.emplace_back(mirBuilder->CreateIntConst(TypeSize / 8, i32PrimTy));
  func->GetMirFunc()->GetBody()->InsertBefore(InsertBefore,
                                              mirBuilder->CreateStmtCall(AsanRBTSafetyCheck->GetPuidx(), args));
  return;
#endif

  size_t accessSizeIndex = TypeSizeToSizeIndex(TypeSize);
  MIRSymbol *addrSymbol = getOrCreateSymbol(mirBuilder, IntPtrTy->GetTypeIndex(), "asan_addr", kStVar, kScAuto,
                                            module->CurFunction(), kScopeLocal);
  DassignNode *dassignNode = mirBuilder->CreateStmtDassign(addrSymbol->GetStIdx(), 0, Addr);
  block->InsertBefore(InsertBefore, dassignNode);
  // save the first pointer
  asanBlock.first = dassignNode;

  // Assign the address to %addr
  MIRType *shadowTy = GlobalTables::GetTypeTable().GetInt8();
  MIRPtrType *shadowPtrTy =
      dynamic_cast<MIRPtrType *>(GlobalTables::GetTypeTable().GetOrCreatePointerType(shadowTy->GetTypeIndex()));
  // Get the address of shadow value
  BaseNode *shadowPtr = memToShadow(mirBuilder->CreateDread(*addrSymbol, IntPtrPrim), *mirBuilder);
  BaseNode *cmpVal = mirBuilder->CreateIntConst(0, shadowTy->GetPrimType());
  // Get the value of shadow memory
  MIRSymbol *shadowValue = getOrCreateSymbol(mirBuilder, shadowTy->GetTypeIndex(), "asan_shadowValue", kStVar, kScAuto,
                                             module->CurFunction(), kScopeLocal);
  dassignNode = mirBuilder->CreateStmtDassign(shadowValue->GetStIdx(), 0,
                                              mirBuilder->CreateExprIread(*shadowTy, *shadowPtrTy, 0, shadowPtr));
  block->InsertBefore(InsertBefore, dassignNode);

  // Check if value != 0
  BinaryNode *cmp = mirBuilder->CreateExprBinary(
      OP_ne, *shadowTy, mirBuilder->CreateDread(*shadowValue, shadowTy->GetPrimType()), cmpVal);
  size_t granularity = 1ULL << Mapping.Scale;

  StmtNode *crashBlock;
  if (TypeSize < 8 * granularity) {
    StmtNode *checkBlock = splitIfAndElseBlock(OP_brfalse, InsertBefore, cmp);
    BinaryNode *cmp2 = createSlowPathCmp(checkBlock, mirBuilder->CreateDread(*addrSymbol, PTY_i64),
                                         mirBuilder->CreateDread(*shadowValue, shadowTy->GetPrimType()), TypeSize);

    crashBlock = splitIfAndElseBlock(OP_brfalse, InsertBefore->GetPrev(), cmp2);

  } else {
    crashBlock = splitIfAndElseBlock(OP_brfalse, InsertBefore, cmp);
  }
  CallNode *crash = generateCrashCode(addrSymbol, IsWrite, accessSizeIndex, SizeArgument);
  crash->InsertBeforeThis(*crashBlock);
  asanBlock.last = InsertBefore->GetPrev();
  asanBlock.crashes.push_back(crash);
  asanBlock.normal = InsertBefore;
  return asanBlock;
}

AsanVirtualBlock AddressSanitizer::instrumentUnusualSizeOrAlignment(StmtNode *InsertBefore, BaseNode *Addr, BlockNode* block,
                                                        uint64_t TypeSize, bool IsWrite) {
  MIRBuilder *mirBuilder = module->GetMIRBuilder();
  AsanVirtualBlock asanBlock;

  BaseNode *size = mirBuilder->CreateIntConst(TypeSize / 8, IntPtrPrim);
  MIRSymbol *addrSymbol = getOrCreateSymbol(mirBuilder, IntPtrTy->GetTypeIndex(), "asan_addr", kStVar, kScAuto,
                                            module->CurFunction(), kScopeLocal);
  // DassignNode *dassignNode = mirBuilder->CreateStmtDassign(addrSymbol->GetStIdx(), 0, Addr);
  // block->InsertBefore(InsertBefore, dassignNode);
  AsanVirtualBlock check1 = instrumentAddress(InsertBefore, Addr, block, 8, IsWrite, size);
  asanBlock.first = check1.first;

  BinaryNode *binaryNode =
      mirBuilder->CreateExprBinary(OP_add, *IntPtrTy, mirBuilder->CreateDread(*addrSymbol, IntPtrPrim),
                                   mirBuilder->CreateIntConst(TypeSize / 8 - 1, IntPtrPrim));
  MIRSymbol *lastByteSymbol = getOrCreateSymbol(mirBuilder, IntPtrTy->GetTypeIndex(), "asan_lastByte", kStVar, kScAuto,
                                                module->CurFunction(), kScopeLocal);
  DassignNode *lastByte = mirBuilder->CreateStmtDassign(lastByteSymbol->GetStIdx(), 0, binaryNode);
  block->InsertBefore(InsertBefore, lastByte);
  AsanVirtualBlock check2 = instrumentAddress(InsertBefore, mirBuilder->CreateDread(*lastByteSymbol, PTY_ptr), block, 8, IsWrite, size);
  asanBlock.last = InsertBefore->GetPrev();
  asanBlock.crashes.insert(asanBlock.crashes.end(), check1.crashes.begin(), check1.crashes.end());
  asanBlock.crashes.insert(asanBlock.crashes.end(), check2.crashes.begin(), check2.crashes.end());
  // TODO: to further improve, we may treat this as two AsanVirtualBlocks
  return asanBlock;
}

BinaryNode *AddressSanitizer::createSlowPathCmp(StmtNode *InsBefore, BaseNode *AddrLong, BaseNode *ShadowValue,
                                                uint64_t TypeSize) {
  MIRBuilder *mirBuilder = module->GetMIRBuilder();
  size_t granularity = static_cast<size_t>(1) << Mapping.Scale;
  // Addr & (Granularity - 1)
  BinaryNode *lastAccessedByte = mirBuilder->CreateExprBinary(OP_band, *IntPtrTy, AddrLong,
                                                              mirBuilder->CreateIntConst(granularity - 1, IntPtrPrim));
  // (Addr & (Granularity - 1)) + size - 1
  if (TypeSize / 8 > 1) {
    lastAccessedByte = mirBuilder->CreateExprBinary(OP_add, *IntPtrTy, lastAccessedByte,
                                                    mirBuilder->CreateIntConst(TypeSize / 8 - 1, IntPtrPrim));
  }
  // (uint8_t) ((Addr & (Granularity-1)) + size - 1)
  MIRSymbol *length = getOrCreateSymbol(mirBuilder, TyIdx(ShadowValue->GetPrimType()), "asan_length", kStVar, kScAuto,
                                        module->CurFunction(), kScopeLocal);
  DassignNode *dassignNode = mirBuilder->CreateStmtDassign(length->GetStIdx(), 0, lastAccessedByte);
  dassignNode->InsertBeforeThis(*InsBefore);
  // ((uint8_t) ((Addr & (Granularity-1)) + size - 1)) >= ShadowValue
  return mirBuilder->CreateExprBinary(OP_ge, *GlobalTables::GetTypeTable().GetTypeFromTyIdx(ShadowValue->GetPrimType()),
                                      mirBuilder->CreateDread(*length, ShadowValue->GetPrimType()), ShadowValue);
}

CallNode *AddressSanitizer::generateCrashCode(MIRSymbol *Addr, bool IsWrite, size_t AccessSizeIndex,
                                              BaseNode *SizeArgument) {
  CallNode *callNode = nullptr;
  MIRBuilder *mirBuilder = module->GetMIRBuilder();
  MapleVector<BaseNode *> args(mirBuilder->GetCurrentFuncCodeMpAllocator()->Adapter());
  args.emplace_back(mirBuilder->CreateDread(*Addr, IntPtrPrim));
  if (SizeArgument) {
    args.emplace_back(SizeArgument);
    callNode = mirBuilder->CreateStmtCall(AsanErrorCallbackSized[IsWrite]->GetPuidx(), args);
  } else {
    callNode = mirBuilder->CreateStmtCall(AsanErrorCallback[IsWrite][AccessSizeIndex]->GetPuidx(), args);
  }
  return callNode;
}

StmtNode *AddressSanitizer::splitIfAndElseBlock(Opcode op, StmtNode *elsePart, const BinaryNode *cmpStmt) {
  MIRBuilder *mirBuilder = module->GetMIRBuilder();
  auto *cmpNode = mirBuilder->CreateExprCompare(
      cmpStmt->GetOpCode(), *GlobalTables::GetTypeTable().GetTypeFromTyIdx(TyIdx(cmpStmt->GetPrimType())),
      *GlobalTables::GetTypeTable().GetTypeFromTyIdx(TyIdx(cmpStmt->GetPrimType())), cmpStmt->Opnd(0),
      cmpStmt->Opnd(1));
  LabelIdx labelIdx = module->CurFunction()->GetLabelTab()->CreateLabel();
  module->CurFunction()->GetLabelTab()->AddToStringLabelMap(labelIdx);
  CondGotoNode *brStmt = mirBuilder->CreateStmtCondGoto(cmpNode, op, labelIdx);
  brStmt->InsertAfterThis(*elsePart);

  brStmt->SetOffset(labelIdx);
  LabelNode *labelStmt = mirBuilder->CreateStmtLabel(labelIdx);
  labelStmt->InsertAfterThis(*elsePart);
  return brStmt;
}

bool AddressSanitizer::isInterestingSymbol(const MIRSymbol &symbol) {
  if (StringUtils::StartsWith(symbol.GetName(), "asan_")) {
    return false;
  }
  if (StringUtils::StartsWith(symbol.GetName(), "retVar_")) {
    /* some variables have names with `retVar_<num>`
    they are inserted by compiler to capture the returned value
    of a function. We skip these variables since they are
    inserted by compiler.
    Currently, there is no flag to identify whether a variable is
    created by users or ArkCC, we have to use this naive approach.
    */
    return false;
  }
  if (StringUtils::StartsWith(symbol.GetName(), "_temp_.shortcircuit.")) {
    return false;
  }
  if (ProcessedSymbols.find(&symbol) != ProcessedSymbols.end()) {
    return ProcessedSymbols[&symbol];
  }
  if (std::find(preAnalysis->usedInAddrof.begin(), preAnalysis->usedInAddrof.end(), &symbol) !=
      preAnalysis->usedInAddrof.end()) {
    return true;
  }
  bool isInteresting = true;

  MIRType *mirType = symbol.GetType();
  isInteresting = isInteresting && isTypeSized(mirType);
  isInteresting = isInteresting && mirType->GetSize() > 0;
  isInteresting = isInteresting && !symbol.IsConst();

  if (mirType->GetKind() == kTypeScalar || mirType->GetKind() == kTypePointer || mirType->GetKind() == kTypeBitField) {
    isInteresting = false;
  }

  ProcessedSymbols[&symbol] = isInteresting;
  return isInteresting;
}

bool AddressSanitizer::isInterestingAlloca(const UnaryNode &unaryNode) {
  if (ProcessedAllocas.find(&unaryNode) != ProcessedAllocas.end()) {
    return ProcessedAllocas[&unaryNode];
  }
  bool isInteresting = true;

  const ConstvalNode *constvalNode = dynamic_cast<const ConstvalNode *>(unaryNode.Opnd(0));
  if (constvalNode) {
    const MIRIntConst *mirConst = dynamic_cast<const MIRIntConst *>(constvalNode->GetConstVal());
    isInteresting = mirConst->GetValue().GetExtValue() > 0;
  }
  ProcessedAllocas[&unaryNode] = isInteresting;
  return isInteresting;
}

void AddressSanitizer::prepareBlockNodeInfo(BlockNode* block, std::vector<int> curDirections) {
  for (StmtNode &stmt : block->GetStmtNodes()) {
    if (stmt.GetOpCode() == OP_block) {
      BlockNode* tmpBlock = dynamic_cast<BlockNode*>(&stmt);
      CHECK_NULL_FATAL(tmpBlock);
      prepareBlockNodeInfo(tmpBlock, curDirections);
    }
    else {
      prepareInfoInternal(&stmt, &stmt, curDirections);
    }
  }
}

void AddressSanitizer::prepareInfo(MeFunction &mefunc){
  std::vector<int> directions;
  prepareBlockNodeInfo(mefunc.GetMirFunc()->GetBody(), directions);
  // for (StmtNode &stmt : mefunc.GetMirFunc()->GetBody()->GetStmtNodes()) {
  //   std::vector<int> directions;
  //   prepareInfoInternal(&stmt, &stmt, directions);
  // }
}

void AddressSanitizer::prepareInfoInternal(StmtNode *stmt, BaseNode *baseNode, std::vector<int> curDirections){
  if (baseNode != nullptr) {
    this->node2stmt[baseNode] = stmt;
    this->node2directions[baseNode] = curDirections;
    for (size_t j = 0; j < baseNode->NumOpnds(); ++j) {
      if (baseNode->GetOpCode() == OP_return) continue;
      std::vector<int> newDirections;
      std::copy(curDirections.begin(), curDirections.end(), back_inserter(newDirections)); 
      newDirections.push_back(j);
      prepareInfoInternal(stmt, baseNode->Opnd(j), newDirections);
    }
  }
}

void AddressSanitizer::DumpSkipPos(){
  std::vector<AsanNodePos>::iterator itr;
  for (itr = this->skipPos.begin(); itr != this->skipPos.end(); ++itr) {
      SrcPosition stmtPos = itr->stmtPos; 
      std::vector<int> directions = itr->directions;
      Opcode op = itr->op;
      int64_t newSize = itr->newSize;
      
      LogInfo::MapleLogger() << "stmtPos:\n";
      stmtPos.Dump();
      LogInfo::MapleLogger() << "directions:\n";
      for (auto dir : directions){
          LogInfo::MapleLogger() << dir << " ";
      }
      LogInfo::MapleLogger() << "\n";
      LogInfo::MapleLogger() << "op: " << int(op) << "\n";
      LogInfo::MapleLogger() << "newSize: " << newSize << "\n";
  }
}

int64 AddressSanitizer::checkNeighborOpt(BaseNode *node){
  if (!neighborOptSwitch) return 0;
  StmtNode * stmt = this->node2stmt[node];
  if (stmt == nullptr)
    return 0;
  SrcPosition srcPos = stmt->GetSrcPos();
  std::vector<int> directions = this->node2directions[node];
  Opcode op = node->GetOpCode();
  for (auto skip : this->skipPos){
    if (skip.stmtPos.IsEq(srcPos)){ 
      if (directions == skip.directions){ 
        if (op == skip.op){
          srcPos.Dump();
          LogInfo::MapleLogger() << "directions:\n";
          for (auto it : directions){
            LogInfo::MapleLogger() << it << " ";
          }
          LogInfo::MapleLogger() << "\nnewSize: " << skip.newSize <<"\n";
          return skip.newSize;
        }
      }
    }
  }
  return 0;
}

}  // namespace maple

#endif